// eslint-disable-next-line max-classes-per-file
import FileRepository from '@ckeditor/ckeditor5-upload/src/filerepository';
import { logWarning } from '@ckeditor/ckeditor5-utils';
import { Plugin } from 'ckeditor5/src/core';

export default class CustomUploadAdapter extends Plugin {
  /**
   * @inheritDoc
   */
  static get requires() {
    return [FileRepository];
  }

  /**
   * @inheritDoc
   */
  static get pluginName() {
    return 'CustomUploadAdapter';
  }

  /**
   * @inheritDoc
   */
  init() {
    const options = this.editor.config.get('customUpload');
    if (!options) {
      return;
    }
    if (!options.uploadUrl) {
      /**
       * The {@link module:upload/uploadconfig~SimpleUploadConfig#uploadUrl `config.simpleUpload.uploadUrl`}
       * configuration required by the {@link module:upload/adapters/simpleuploadadapter~SimpleUploadAdapter `SimpleUploadAdapter`}
       * is missing. Make sure the correct URL is specified for the image upload to work properly.
       *
       * @error simple-upload-adapter-missing-uploadurl
       */
      logWarning('simple-upload-adapter-missing-uploadurl');
      return;
    }
    this.editor.plugins.get(FileRepository).createUploadAdapter = (loader) => {
      return new Adapter(loader, options);
    };
  }
}

/**
 * Upload adapter.
 */
class Adapter {
  /**
   * Creates a new adapter instance.
   */
  constructor(loader, options) {
    this.loader = loader;
    this.options = options;
  }

  /**
   * Starts the upload process.
   *
   * @see module:upload/filerepository~UploadAdapter#upload
   */
  upload() {
    return this.loader.file.then(
      (file) =>
        new Promise((resolve, reject) => {
          this.initRequest();
          this.initListeners(resolve, reject, file);
          this.sendRequest(file);
        }),
    );
  }

  /**
   * Aborts the upload process.
   *
   * @see module:upload/filerepository~UploadAdapter#abort
   */
  abort() {
    if (this.xhr) {
      this.xhr.abort();
    }
  }

  /**
   * Initializes the `XMLHttpRequest` object using the URL specified as
   * {@link module:upload/uploadconfig~SimpleUploadConfig#uploadUrl `simpleUpload.uploadUrl`} in the editor's
   * configuration.
   */
  initRequest() {
    // eslint-disable-next-line no-multi-assign
    const xhr = (this.xhr = new XMLHttpRequest());
    xhr.open('POST', this.options.uploadUrl, true);
    xhr.responseType = 'json';
  }

  /**
   * Initializes XMLHttpRequest listeners
   *
   * @param resolve Callback function to be called when the request is successful.
   * @param reject Callback function to be called when the request cannot be completed.
   * @param file Native File object.
   */
  initListeners(resolve, reject, file) {
    const xhr = this.xhr;
    const loader = this.loader;
    const options = this.options;
    const genericErrorText = `Couldn't upload file: ${file.name}.`;
    xhr.addEventListener('error', () => reject(genericErrorText));
    xhr.addEventListener('abort', () => reject());
    // eslint-disable-next-line consistent-return
    xhr.addEventListener('load', () => {
      const response = xhr.response;
      if (!response || response.error) {
        if (options.error) {
          return reject(options.error(response));
        }
        return reject(response && response.error && response.error.message ? response.error.message : genericErrorText);
      }
      let urls;
      if (options.success) {
        urls = options.success(response);
      } else {
        urls = response.url ? { default: response.url } : response.urls;
      }
      // Resolve with the normalized `urls` property and pass the rest of the response
      // to allow customizing the behavior of features relying on the upload adapters.
      resolve({
        ...response,
        urls,
      });
    });
    // Upload progress when it is supported.
    /* istanbul ignore else -- @preserve */
    if (xhr.upload) {
      xhr.upload.addEventListener('progress', (evt) => {
        if (evt.lengthComputable) {
          loader.uploadTotal = evt.total;
          loader.uploaded = evt.loaded;
        }
      });
    }
  }

  /**
   * Prepares the data and sends the request.
   *
   * @param file File instance to be uploaded.
   */
  sendRequest(file) {
    // Set headers if specified.
    const options = this.options;
    const headers = options.headers || {};
    // Use the withCredentials flag if specified.
    const withCredentials = options.withCredentials || false;
    for (const headerName of Object.keys(headers)) {
      this.xhr.setRequestHeader(headerName, headers[headerName]);
    }
    this.xhr.withCredentials = withCredentials;
    // Prepare the form data.
    let data = new FormData();
    data.append('upload', file);
    if (options.beforeUpload) {
      data = options.beforeUpload(data, file);
    }
    // Send the request.
    this.xhr.send(data);
  }
}
